{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|default_exp data.transforms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Time Series Data Augmentation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ">Functions used to transform TSTensors (Data Augmentation)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "from tsai.imports import *\n",
    "from scipy.interpolate import CubicSpline\n",
    "from scipy.ndimage import convolve1d\n",
    "from fasttransform import compose_tfms\n",
    "from fastai.vision.augment import RandTransform\n",
    "from tsai.utils import *\n",
    "from tsai.data.core import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tsai.data.core import TSCategorize\n",
    "from tsai.data.external import get_UCR_data\n",
    "from tsai.data.preprocessing import TSStandardize"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dsid = 'NATOPS'\n",
    "X, y, splits = get_UCR_data(dsid, return_split=False)\n",
    "tfms = [None, TSCategorize()]\n",
    "batch_tfms = TSStandardize()\n",
    "dls = get_ts_dls(X, y, tfms=tfms, splits=splits, batch_tfms=batch_tfms, bs=128)\n",
    "xb, yb = next(iter(dls.train))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSIdentity(RandTransform):\n",
    "    \"Applies the identity tfm to a `TSTensor` batch\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=None, **kwargs):\n",
    "        self.magnitude = magnitude\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor): return o"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSIdentity()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "# partial(TSShuffle_HLs, ex=0),\n",
    "class TSShuffle_HLs(RandTransform):\n",
    "    \"Randomly shuffles HIs/LOs of an OHLC `TSTensor` batch\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=1., ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        timesteps = o.shape[-1] // 4\n",
    "        pos_rand_list = random_choice(np.arange(timesteps),size=random.randint(1, timesteps),replace=False)\n",
    "        rand_list = pos_rand_list * 4\n",
    "        highs = rand_list + 1\n",
    "        lows = highs + 1\n",
    "        a = np.vstack([highs, lows]).flatten('F')\n",
    "        b = np.vstack([lows, highs]).flatten('F')\n",
    "        output = o.clone()\n",
    "        output[...,a] = output[...,b]\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSShuffle_HLs()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "# partial(TSShuffleSteps, ex=0),\n",
    "class TSShuffleSteps(RandTransform):\n",
    "    \"Randomly shuffles consecutive sequence datapoints in batch\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=1., ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        odd = 1 - o.shape[-1]%2\n",
    "        r = np.random.randint(2)\n",
    "        timesteps = o.shape[-1] // 2\n",
    "        pos_rand_list = random_choice(np.arange(0, timesteps - r * odd), size=random.randint(1, timesteps - r * odd),replace=False) * 2 + 1 + r\n",
    "        a = np.vstack([pos_rand_list, pos_rand_list - 1]).flatten('F')\n",
    "        b = np.vstack([pos_rand_list - 1, pos_rand_list]).flatten('F')\n",
    "        output = o.clone()\n",
    "        output[...,a] = output[...,b]\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAigAAAGdCAYAAAA44ojeAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAH6VJREFUeJzt3X2QldV9B/Afy7soIKSAjICmtQUUxaAi0bapUAihjg5MEjuE0IQxUwaMQEsMLaIDRgxN1GoRo2PBTKUk/oGJxBARDU4LCGLtIFiijRmoBGhreM3wvp1zZu6Wpdq4sMmeu/v5zByffZ7n7L3nPqy733tentuqtra2NgAAClLT1A0AADidgAIAFEdAAQCKI6AAAMURUACA4ggoAEBxBBQAoDgCCgBQnDZRhU6ePBk7d+6M8847L1q1atXUzQEAPoR0b9gDBw5E7969o6ampvkFlBRO+vTp09TNAADOwI4dO+LCCy9sfgEl9ZxUXmDnzp2bujkAwIewf//+3MFQ+Tve7AJKZVgnhRMBBQCqy4eZnmGSLABQHAEFACiOgAIAFEdAAQCKI6AAAMURUACA4ggoAEBxBBQAoDgCCgBQHAEFACiOgAIAFEdAAQCKI6AAAMURUACA4rRp6gYAUP0u+uoPmroJ8bP7xjR1E2hEelAAgOIIKABAcQQUAKA45qAAFMzcDloqPSgAQHH0oADQIpTQG5Xokfpw9KAAAMURUACA4ggoAEBxBBQAoDgCCgBQHAEFACiOgAIAFMd9UIAWyT0xoGx6UACA6g8o7777bnzuc5+L7t27R8eOHWPQoEHx6quv1p2vra2NOXPmxAUXXJDPjxgxIt566616j/Hee+/F+PHjo3PnztG1a9eYNGlSHDx4sHFeEQDQsgLKL37xi7juuuuibdu28cMf/jC2bt0a3/zmN+P888+vq7NgwYJ46KGH4tFHH41XXnklOnXqFKNGjYrDhw/X1UnhZMuWLbFq1apYsWJFvPzyy/GlL32pcV8ZANAy5qB8/etfjz59+sTixYvrjl188cX1ek8efPDBmD17dtx000352Le//e3o2bNnPPPMM3HLLbfEm2++GStXroyNGzfGVVddles8/PDD8alPfSq+8Y1vRO/evRvv1QEAzb8H5fvf/34OFZ/+9KejR48eceWVV8bjjz9ed/6dd96JXbt25WGdii5dusTQoUNj3bp1eT9t07BOJZwkqX5NTU3ucXk/R44cif3799crAEDz1aCA8tOf/jQWLVoUl1xySfzoRz+KyZMnx5e//OV48skn8/kUTpLUY3KqtF85l7Yp3JyqTZs20a1bt7o6p5s/f34OOpWSenEAgOarQUM8J0+ezD0f9957b95PPShvvPFGnm8yceLEX1cbY9asWTFjxoy6/dSD0tJDiiWSADRnDepBSStzBg4cWO/YgAEDYvv27fnrXr165e3u3bvr1Un7lXNpu2fPnnrnjx8/nlf2VOqcrn379nnFz6kFAGi+GhRQ0gqebdu21Tv2k5/8JPr161c3YTaFjNWrV9fr7UhzS4YNG5b303bv3r2xadOmujovvvhi7p1Jc1UAABo0xDN9+vT4+Mc/nod4PvOZz8SGDRviscceyyVp1apVTJs2Le655548TyUFljvvvDOvzLn55pvrelw++clPxq233pqHho4dOxZTp07NK3ys4IHqZ/gR+I0HlKuvvjqWL1+e54TMnTs3B5C0rDjd16TiK1/5Shw6dCjf1yT1lFx//fV5WXGHDh3q6jz11FM5lAwfPjyv3hk3bly+dwoAwBl9Fs+f/Mmf5PJBUi9KCi+pfJC0Ymfp0qX+BaAKeyf0TAC/CT6LBwAojk8zptm/40+86weoLgIKFBKkhCiA/2WIBwAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQCo7oBy9913R6tWreqV/v37150/fPhwTJkyJbp37x7nnntujBs3Lnbv3l3vMbZv3x5jxoyJc845J3r06BEzZ86M48ePN94rAgCqXpuGfsOll14aL7zwwv8+QJv/fYjp06fHD37wg3j66aejS5cuMXXq1Bg7dmz88z//cz5/4sSJHE569eoVa9eujZ///Ofx+c9/Ptq2bRv33ntvY70mAKClBZQUSFLAON2+ffviiSeeiKVLl8YNN9yQjy1evDgGDBgQ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pk3x6quvxg033BA33XRTbNmyJZ+fPn16PPvss/H000/HmjVrYufOnTF27Ni67z9x4kSMGTMmjh49GmvXro0nn3wylixZkkMOAEBFg7otbrzxxnr7X/va13KPyvr163N4eeKJJ2Lp0qU5uCSLFy+OAQMG5PPXXnttPP/887F169Z44YUXcq/K4MGDY968eXHHHXfE3XffHe3atWtIcwCAZuqMx1VSb0jqKTl06FAMGzYs96ocO3YsRowYUVenf//+0bdv31i3bl0OKGk7aNCgekM+aRgoDfmkXpgrr7zyfZ8rDRelUrF///5o7t1rTd21BgBVNUl28+bNee5I+/bt48///M9j+fLlMXDgwNi1a1fuAenatWu9+imMpHNJ2p4aTirnK+c+SJqj0qVLl7rSp0+fhjYbAGjOAeX3fu/34vXXX49XXnkl93ykSa5p2ObXadasWbFv3766smPHjl/r8wEAVTbEk3pJfud3fid/PWTIkNi4cWP87d/+bXz2s5/Nk1/37t1brxdl9+7d0atXr/x12m7YsKHe46XzlXMfJPXWpAIAtAxnfR+UkydP5vkhKay0bds2Vq9eXXdu27ZteVlxmqOSpG0aItqzZ09dnVWrVkXnzp3zMBEAQIN7UNJQS7rnSZr4euDAgbxi58c//nH86Ec/ynNDJk2aFDNmzIhu3brl0HHbbbflUJImyCYjR47MQWTChAmxYMGCPO9k9uzZMWXKFD0kAMCZBZTU8/H5z38+fv7zn+dAcvnll+dw8sd//Mf5/AMPPBA1NTUxbty4ejdqq2jdunWsWLEiz11JwaVTp055DsvcuXMb0gwAoJlrUEBJ9zn5/3To0CEWLlyYywfp169fPPfccw15WgCghfFZPABAcQQUAKA4AgoAUBwBBQAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQAojoACABRHQAEAiiOgAADFEVAAgOIIKABAcQQUAKA4AgoAUBwBBQCo7oAyf/78uPrqq+O8886LHj16xM033xzbtm2r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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "t = TSTensor(torch.arange(11).float())\n",
    "tt_ = []\n",
    "for _ in range(1000):\n",
    "    tt = TSShuffleSteps()(t, split_idx=0)\n",
    "    test_eq(len(set(tt.tolist())), len(t))\n",
    "    test_ne(tt, t)\n",
    "    tt_.extend([t for i,t in enumerate(tt) if t!=i])\n",
    "x, y = np.unique(tt_, return_counts=True) # This is to visualize distribution which should be equal for all and half for first and last items\n",
    "plt.bar(x, y);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSGaussianNoise(RandTransform):\n",
    "    \"Applies additive or multiplicative gaussian noise\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=.5, additive=True, ex=None, **kwargs):\n",
    "        self.magnitude, self.additive, self.ex = magnitude, additive, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if self.magnitude <= 0: return o\n",
    "        noise = self.magnitude * torch.randn_like(o)\n",
    "        if self.ex is None:\n",
    "            if self.additive: return o + noise\n",
    "            else: return o * (1 + noise)\n",
    "        else:\n",
    "            if self.additive: output = o + noise\n",
    "            else: output = o * (1 + noise)\n",
    "        output[..., self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSGaussianNoise(.1, additive=True)(xb, split_idx=0).shape, xb.shape)\n",
    "test_eq(TSGaussianNoise(.1, additive=False)(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSMagAddNoise(RandTransform):\n",
    "    \"Applies additive noise on the y-axis for each step of a `TSTensor` batch\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=1, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        # output = o + torch.normal(0, o.std() * self.magnitude, o.shape, dtype=o.dtype, device=o.device)\n",
    "        output = o + torch.normal(0, 1/3, o.shape, dtype=o.dtype, device=o.device) * (o[..., 1:] - o[..., :-1]).std(2, keepdims=True) * self.magnitude\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output\n",
    "\n",
    "class TSMagMulNoise(RandTransform):\n",
    "    \"Applies multiplicative noise on the y-axis for each step of a `TSTensor` batch\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=1, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        noise = torch.normal(1, self.magnitude * .025, o.shape, dtype=o.dtype, device=o.device)\n",
    "        output = o * noise\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSMagAddNoise()(xb, split_idx=0).shape, xb.shape)\n",
    "test_eq(TSMagMulNoise()(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSMagAddNoise()(xb, split_idx=0), xb)\n",
    "test_ne(TSMagMulNoise()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "def random_curve_generator(o, magnitude=0.1, order=4, noise=None):\n",
    "    seq_len = o.shape[-1]\n",
    "    f = CubicSpline(np.linspace(-seq_len, 2 * seq_len - 1, 3 * (order - 1) + 1, dtype=int),\n",
    "                    np.random.normal(loc=1.0, scale=magnitude, size=3 * (order - 1) + 1), axis=-1)\n",
    "    return f(np.arange(seq_len))\n",
    "\n",
    "def random_cum_curve_generator(o, magnitude=0.1, order=4, noise=None):\n",
    "    x = random_curve_generator(o, magnitude=magnitude, order=order, noise=noise).cumsum()\n",
    "    x -= x[0]\n",
    "    x /= x[-1]\n",
    "    x = np.clip(x, 0, 1)\n",
    "    return x * (o.shape[-1] - 1)\n",
    "\n",
    "def random_cum_noise_generator(o, magnitude=0.1, noise=None):\n",
    "    seq_len = o.shape[-1]\n",
    "    x = np.clip(np.ones(seq_len) + np.random.normal(loc=0, scale=magnitude, size=seq_len), 0, 1000).cumsum()\n",
    "    x -= x[0]\n",
    "    x /= x[-1]\n",
    "    return x * (o.shape[-1] - 1)\n",
    "\n",
    "def random_cum_linear_generator(o, magnitude=0.1):\n",
    "    seq_len = o.shape[-1]\n",
    "    win_len = int(round(seq_len * np.random.rand() * magnitude))\n",
    "    if win_len == seq_len: return np.arange(o.shape[-1])\n",
    "    start = np.random.randint(0, seq_len - win_len)\n",
    "    # mult between .5 and 2\n",
    "    rand = np.random.rand()\n",
    "    mult = 1 + rand\n",
    "    if np.random.randint(2): mult = 1 - rand/2\n",
    "    x = np.ones(seq_len)\n",
    "    x[start : start + win_len] = mult\n",
    "    x = x.cumsum()\n",
    "    x -= x[0]\n",
    "    x /= x[-1]\n",
    "    return np.clip(x, 0, 1) * (seq_len - 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSTimeNoise(RandTransform):\n",
    "    \"Applies noise to each step in the x-axis of a `TSTensor` batch based on smooth random curve\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        f = CubicSpline(np.arange(o.shape[-1]), o.cpu(), axis=-1)\n",
    "        output = o.new(f(random_cum_noise_generator(o, magnitude=self.magnitude)))\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSTimeNoise()(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSTimeNoise()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSMagWarp(RandTransform):\n",
    "    \"Applies warping to the y-axis of a `TSTensor` batch based on a smooth random curve\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.02, ord=4, ex=None, **kwargs):\n",
    "        self.magnitude, self.ord, self.ex = magnitude, ord, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if self.magnitude and self.magnitude <= 0: return o\n",
    "        y_mult = random_curve_generator(o, magnitude=self.magnitude, order=self.ord)\n",
    "        output = o * o.new(y_mult)\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSMagWarp()(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSMagWarp()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSTimeWarp(RandTransform):\n",
    "    \"Applies time warping to the x-axis of a `TSTensor` batch based on a smooth random curve\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ord=6, ex=None, **kwargs):\n",
    "        self.magnitude, self.ord, self.ex = magnitude, ord, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        f = CubicSpline(np.arange(o.shape[-1]), o.cpu(), axis=-1)\n",
    "        output = o.new(f(random_cum_curve_generator(o, magnitude=self.magnitude, order=self.ord)))\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSTimeWarp()(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSTimeWarp()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSWindowWarp(RandTransform):\n",
    "    \"\"\"Applies window slicing to the x-axis of a `TSTensor` batch based on a random linear curve based on\n",
    "    https://halshs.archives-ouvertes.fr/halshs-01357973/document\"\"\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0 or self.magnitude >= 1: return o\n",
    "        f = CubicSpline(np.arange(o.shape[-1]), o.cpu(), axis=-1)\n",
    "        output = o.new(f(random_cum_linear_generator(o, magnitude=self.magnitude)))\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSWindowWarp()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSMagScale(RandTransform):\n",
    "    \"Applies scaling to the y-axis of a `TSTensor` batch based on a scalar\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.5, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        rand = random_half_normal()\n",
    "        scale = (1 - (rand  * self.magnitude)/2) if random.random() > 1/3 else (1 + (rand  * self.magnitude))\n",
    "        output = o * scale\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output\n",
    "\n",
    "class TSMagScalePerVar(RandTransform):\n",
    "    \"Applies per_var scaling to the y-axis of a `TSTensor` batch based on a scalar\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.5, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        s = [1] * o.ndim\n",
    "        s[-2] = o.shape[-2]\n",
    "        rand = random_half_normal_tensor(s, device=o.device)\n",
    "        scale = (1 - (rand  * self.magnitude)/2) if random.random() > 1/3 else (1 + (rand  * self.magnitude))\n",
    "        output = o * scale\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output\n",
    "\n",
    "TSMagScaleByVar = TSMagScalePerVar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSMagScale()(xb, split_idx=0).shape, xb.shape)\n",
    "test_eq(TSMagScalePerVar()(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSMagScale()(xb, split_idx=0), xb)\n",
    "test_ne(TSMagScalePerVar()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "def test_interpolate(mode=\"linear\"):\n",
    "\n",
    "    assert mode in [\"nearest\", \"linear\", \"area\"], \"Mode must be 'nearest', 'linear' or 'area'.\"\n",
    "\n",
    "    # Create a 1D tensor\n",
    "    tensor = torch.randn(1, 1, 8, device=default_device())\n",
    "\n",
    "    try:\n",
    "        # Try to interpolate using linear mode\n",
    "        result = F.interpolate(tensor, scale_factor=2, mode=mode)\n",
    "        return True\n",
    "    except NotImplementedError as e:\n",
    "        print(f\"{mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "        print(\"Error:\", e)\n",
    "        return False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Run the test\n",
    "test_interpolate('linear')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_interpolate('nearest')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomResizedCrop(RandTransform):\n",
    "    \"Randomly amplifies a sequence focusing on a random section of the steps\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, size=None, scale=None, ex=None, mode='nearest', **kwargs):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            size: None, int or float\n",
    "            scale: None or tuple of 2 floats 0 < float <= 1\n",
    "            mode:  'nearest' | 'linear' | 'area'\n",
    "\n",
    "        \"\"\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.ex, self.mode = magnitude, ex, mode\n",
    "        if scale is not None:\n",
    "            assert is_listy(scale) and len(scale) == 2 and min(scale) > 0 and min(scale) <= 1, \"scale must be a tuple with 2 floats 0 < float <= 1\"\n",
    "        self.size,self.scale = size,scale\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        seq_len = o.shape[-1]\n",
    "        if self.size is not None:\n",
    "            size = self.size if isinstance(self.size, Integral) else int(round(self.size * seq_len))\n",
    "        else:\n",
    "            size = seq_len\n",
    "        if self.scale is not None:\n",
    "            lambd = np.random.uniform(self.scale[0], self.scale[1])\n",
    "        else:\n",
    "            lambd = np.random.beta(self.magnitude, self.magnitude)\n",
    "            lambd = max(lambd, 1 - lambd)\n",
    "        win_len = int(round(seq_len * lambd))\n",
    "        if win_len == seq_len:\n",
    "            if size == seq_len: return o\n",
    "            _slice = slice(None)\n",
    "        else:\n",
    "            start = np.random.randint(0, seq_len - win_len)\n",
    "            _slice = slice(start, start + win_len)\n",
    "        return F.interpolate(o[..., _slice], size=size, mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)\n",
    "\n",
    "TSRandomZoomIn = TSRandomResizedCrop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    test_eq(TSRandomResizedCrop(.5)(xb, split_idx=0).shape, xb.shape)\n",
    "    test_ne(TSRandomResizedCrop(size=.8, scale=(.5, 1))(xb, split_idx=0).shape, xb.shape)\n",
    "    test_ne(TSRandomResizedCrop(size=20, scale=(.5, 1))(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSWindowSlicing(RandTransform):\n",
    "    \"Randomly extracts an resize a ts slice based on https://halshs.archives-ouvertes.fr/halshs-01357973/document\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.ex, self.mode = magnitude, ex, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0 or self.magnitude >= 1: return o\n",
    "        seq_len = o.shape[-1]\n",
    "        win_len = int(round(seq_len * (1 - self.magnitude)))\n",
    "        if win_len == seq_len: return o\n",
    "        start = np.random.randint(0, seq_len - win_len)\n",
    "        return F.interpolate(o[..., start : start + win_len], size=seq_len, mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    test_eq(TSWindowSlicing()(xb, split_idx=0).shape, xb.shape)\n",
    "    test_ne(TSWindowSlicing()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomZoomOut(RandTransform):\n",
    "    \"Randomly compresses a sequence on the x-axis\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.ex, self.mode = magnitude, ex, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        seq_len = o.shape[-1]\n",
    "        lambd = np.random.beta(self.magnitude, self.magnitude)\n",
    "        lambd = max(lambd, 1 - lambd)\n",
    "        win_len = int(round(seq_len * lambd))\n",
    "        if win_len == seq_len: return o\n",
    "        start = (seq_len - win_len) // 2\n",
    "        output = torch.zeros_like(o, dtype=o.dtype, device=o.device)\n",
    "        interp = F.interpolate(o, size=win_len, mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)\n",
    "        output[..., start:start + win_len] = o.new(interp)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    test_eq(TSRandomZoomOut(.5)(xb, split_idx=0).shape, xb.shape)#"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomTimeScale(RandTransform):\n",
    "    \"Randomly amplifies/ compresses a sequence on the x-axis keeping the same length\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.ex, self.mode = magnitude, ex, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        if np.random.rand() <= 0.5: return TSRandomZoomIn(magnitude=self.magnitude, ex=self.ex, mode=self.mode)(o, split_idx=0)\n",
    "        else: return TSRandomZoomOut(magnitude=self.magnitude, ex=self.ex, mode=self.mode)(o, split_idx=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    test_eq(TSRandomTimeScale(.5)(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomTimeStep(RandTransform):\n",
    "    \"Compresses a sequence on the x-axis by randomly selecting sequence steps and interpolating to previous size\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.02, ex=None, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.ex, self.mode = magnitude, ex, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        seq_len = o.shape[-1]\n",
    "        new_seq_len = int(round(seq_len * max(.5, (1 - np.random.rand() * self.magnitude))))\n",
    "        if  new_seq_len == seq_len: return o\n",
    "        timesteps = np.sort(random_choice(np.arange(seq_len),new_seq_len, replace=False))\n",
    "        output = F.interpolate(o[..., timesteps], size=seq_len, mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    test_eq(TSRandomTimeStep()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "\n",
    "class TSResampleSteps(RandTransform):\n",
    "    \"Transform that randomly selects and sorts sequence steps (with replacement) maintaining the sequence length\"\n",
    "\n",
    "    order = 90\n",
    "    def __init__(self, step_pct=1., same_seq_len=True, magnitude=None, **kwargs):\n",
    "        assert step_pct > 0, 'seq_len_pct must be subsample > 0'\n",
    "        self.step_pct, self.same_seq_len = step_pct, same_seq_len\n",
    "        super().__init__(**kwargs)\n",
    "\n",
    "    def encodes(self, o: TSTensor):\n",
    "        S = o.shape[-1]\n",
    "        if isinstance(self.step_pct, tuple):\n",
    "            step_pct = np.random.rand() * (self.step_pct[1] - self.step_pct[0]) + self.step_pct[0]\n",
    "        else:\n",
    "            step_pct = self.step_pct\n",
    "        if step_pct != 1 and self.same_seq_len:\n",
    "            idxs = np.sort(np.tile(random_choice(S, round(S * step_pct), True), math.ceil(1 / step_pct))[:S])\n",
    "        else:\n",
    "            idxs = np.sort(random_choice(S, round(S * step_pct), True))\n",
    "        return o[..., idxs]\n",
    "\n",
    "TSSubsampleSteps = TSResampleSteps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSResampleSteps(step_pct=.9, same_seq_len=False)(xb, split_idx=0).shape[-1], round(.9*xb.shape[-1]))\n",
    "test_eq(TSResampleSteps(step_pct=.9, same_seq_len=True)(xb, split_idx=0).shape[-1], xb.shape[-1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSBlur(RandTransform):\n",
    "    \"Blurs a sequence applying a filter of type [1, 0, 1]\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=1., ex=None, filt_len=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        if filt_len is None:\n",
    "            filterargs = [1, 0, 1]\n",
    "        else:\n",
    "            filterargs = ([1] * max(1, filt_len // 2) + [0] + [1] * max(1, filt_len // 2))\n",
    "        self.filterargs = np.array(filterargs)\n",
    "        self.filterargs = self.filterargs/self.filterargs.sum()\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        output = o.new(convolve1d(o.cpu(), self.filterargs, mode='nearest'))\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSBlur(filt_len=7)(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSBlur()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSSmooth(RandTransform):\n",
    "    \"Smoothens a sequence applying a filter of type [1, 5, 1]\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=1., ex=None, filt_len=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        self.filterargs = np.array([1, 5, 1])\n",
    "        if filt_len is None:\n",
    "            filterargs = [1, 5, 1]\n",
    "        else:\n",
    "            filterargs = ([1] * max(1, filt_len // 2) + [5] + [1] * max(1, filt_len // 2))\n",
    "        self.filterargs = np.array(filterargs)\n",
    "        self.filterargs = self.filterargs/self.filterargs.sum()\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        output = o.new(convolve1d(o.cpu(), self.filterargs, mode='nearest'))\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSSmooth(filt_len=7)(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSSmooth()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "def maddest(d, axis=None):\n",
    "    #Mean Absolute Deviation\n",
    "    return np.mean(np.absolute(d - np.mean(d, axis=axis)), axis=axis)\n",
    "\n",
    "class TSFreqDenoise(RandTransform):\n",
    "    \"Denoises a sequence applying a wavelet decomposition method\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, wavelet='db4', level=2, thr=None, thr_mode='hard', pad_mode='per', **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        self.wavelet, self.level, self.thr, self.thr_mode, self.pad_mode = wavelet, level, thr, thr_mode, pad_mode\n",
    "        super().__init__(**kwargs)\n",
    "        try:\n",
    "            import pywt\n",
    "        except ImportError:\n",
    "            raise ImportError('You need to install pywt to run TSFreqDenoise')\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        \"\"\"\n",
    "        1. Adapted from waveletSmooth function found here:\n",
    "        http://connor-johnson.com/2016/01/24/using-pywavelets-to-remove-high-frequency-noise/\n",
    "        2. Threshold equation and using hard mode in threshold as mentioned\n",
    "        in section '3.2 denoising based on optimized singular values' from paper by Tomas Vantuch:\n",
    "        http://dspace.vsb.cz/bitstream/handle/10084/133114/VAN431_FEI_P1807_1801V001_2018.pdf\n",
    "        \"\"\"\n",
    "        seq_len = o.shape[-1]\n",
    "        # Decompose to get the wavelet coefficients\n",
    "        coeff = pywt.wavedec(o.cpu(), self.wavelet, mode=self.pad_mode)\n",
    "        # Calculate sigma for threshold as defined in http://dspace.vsb.cz/bitstream/handle/10084/133114/VAN431_FEI_P1807_1801V001_2018.pdf\n",
    "        # As noted by @harshit92 MAD referred to in the paper is Mean Absolute Deviation not Median Absolute Deviation\n",
    "        sigma = (1/0.6745) * maddest(coeff[-self.level])\n",
    "        # Calculate the univeral threshold\n",
    "        uthr = sigma * np.sqrt(2*np.log(seq_len)) * (1 if self.thr is None else self.magnitude)\n",
    "        coeff[1:] = (pywt.threshold(c, value=uthr, mode=self.thr_mode) for c in coeff[1:])\n",
    "        # Reconstruct the signal using the thresholded coefficients\n",
    "        output = o.new(pywt.waverec(coeff, self.wavelet, mode=self.pad_mode)[..., :seq_len])\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try: import pywt\n",
    "except ImportError: pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if 'pywt' in dir():\n",
    "    test_eq(TSFreqDenoise()(xb, split_idx=0).shape, xb.shape)\n",
    "    test_ne(TSFreqDenoise()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomFreqNoise(RandTransform):\n",
    "    \"Applys random noise using a wavelet decomposition method\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, wavelet='db4', level=2, mode='constant', **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        self.wavelet, self.level, self.mode = wavelet, level, mode\n",
    "        super().__init__(**kwargs)\n",
    "        try:\n",
    "            import pywt\n",
    "        except ImportError:\n",
    "            raise ImportError('You need to install pywt to run TSRandomFreqNoise')\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        self.level = 1 if self.level is None else self.level\n",
    "        coeff = pywt.wavedec(o.cpu(), self.wavelet, mode=self.mode, level=self.level)\n",
    "        coeff[1:] = [c * (1 + 2 * (np.random.rand() - 0.5) * self.magnitude) for c in coeff[1:]]\n",
    "        output = o.new(pywt.waverec(coeff, self.wavelet, mode=self.mode)[..., :o.shape[-1]])\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if 'pywt' in dir():\n",
    "    test_eq(TSRandomFreqNoise()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomResizedLookBack(RandTransform):\n",
    "    \"Selects a random number of sequence steps starting from the end and return an output of the same shape\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.mode = magnitude, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        seq_len = o.shape[-1]\n",
    "        lambd = np.random.beta(self.magnitude, self.magnitude)\n",
    "        lambd = min(lambd, 1 - lambd)\n",
    "        output = o.clone()[..., int(round(lambd * seq_len)):]\n",
    "        return F.interpolate(output, size=seq_len, mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    for i in range(100):\n",
    "        o = TSRandomResizedLookBack()(xb, split_idx=0)\n",
    "        test_eq(o.shape[-1], xb.shape[-1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomLookBackOut(RandTransform):\n",
    "    \"Selects a random number of sequence steps starting from the end and set them to zero\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, **kwargs):\n",
    "        self.magnitude = magnitude\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        seq_len = o.shape[-1]\n",
    "        lambd = np.random.beta(self.magnitude, self.magnitude)\n",
    "        lambd = min(lambd, 1 - lambd)\n",
    "        output = o.clone()\n",
    "        output[..., :int(round(lambd * seq_len))] = 0\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(100):\n",
    "    o = TSRandomLookBackOut()(xb, split_idx=0)\n",
    "    test_eq(o.shape[-1], xb.shape[-1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSVarOut(RandTransform):\n",
    "    \"Set the value of a random number of variables to zero\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.05, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        in_vars = o.shape[-2]\n",
    "        if in_vars == 1: return o\n",
    "        lambd = np.random.beta(self.magnitude, self.magnitude)\n",
    "        lambd = min(lambd, 1 - lambd)\n",
    "        p = np.arange(in_vars).cumsum()\n",
    "        p = p/p[-1]\n",
    "        p = p / p.sum()\n",
    "        p = p[::-1]\n",
    "        out_vars = random_choice(np.arange(in_vars), int(round(lambd * in_vars)), p=p, replace=False)\n",
    "        if len(out_vars) == 0:  return o\n",
    "        output = o.clone()\n",
    "        output[...,out_vars,:] = 0\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSVarOut()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSCutOut(RandTransform):\n",
    "    \"Sets a random section of the sequence to zero\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.05, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        seq_len = o.shape[-1]\n",
    "        lambd = np.random.beta(self.magnitude, self.magnitude)\n",
    "        lambd = min(lambd, 1 - lambd)\n",
    "        win_len = int(round(seq_len * lambd))\n",
    "        start = np.random.randint(-win_len + 1, seq_len)\n",
    "        end = start + win_len\n",
    "        start = max(0, start)\n",
    "        end = min(end, seq_len)\n",
    "        output = o.clone()\n",
    "        output[..., start:end] = 0\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSCutOut()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSTimeStepOut(RandTransform):\n",
    "    \"Sets random sequence steps to zero\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.05, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        magnitude = min(.5, self.magnitude)\n",
    "        seq_len = o.shape[-1]\n",
    "        timesteps = np.sort(random_choice(np.arange(seq_len), int(round(seq_len * magnitude)), replace=False))\n",
    "        output = o.clone()\n",
    "        output[..., timesteps] = 0\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSTimeStepOut()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomCropPad(RandTransform):\n",
    "    \"Crops a section of the sequence of a random length\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.05, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        seq_len = o.shape[-1]\n",
    "        lambd = np.random.beta(self.magnitude, self.magnitude)\n",
    "        lambd = max(lambd, 1 - lambd)\n",
    "        win_len = int(round(seq_len * lambd))\n",
    "        if win_len == seq_len: return o\n",
    "        start = np.random.randint(0, seq_len - win_len)\n",
    "        output = torch.zeros_like(o, dtype=o.dtype, device=o.device)\n",
    "        output[..., start : start + win_len] = o[..., start : start + win_len]\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSRandomCropPad()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSMaskOut(RandTransform):\n",
    "    \"\"\"Applies a random mask\"\"\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, compensate:bool=False, ex=None, **kwargs):\n",
    "        store_attr()\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        mask = torch.rand_like(o) > (1 - self.magnitude)\n",
    "        if self.compensate: # per sample and feature\n",
    "            mean_per_seq = (torch.max(torch.ones(1, device=mask.device), torch.sum(mask, dim=-1).unsqueeze(-1)) / mask.shape[-1])\n",
    "            output = o.masked_fill(mask, 0) / (1 - mean_per_seq)\n",
    "        else:\n",
    "            output = o.masked_fill(mask, 0)\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSMaskOut()(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSMaskOut()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSInputDropout(RandTransform):\n",
    "    \"\"\"Applies input dropout with required_grad=False\"\"\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0., ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        self.dropout = nn.Dropout(magnitude)\n",
    "        super().__init__(**kwargs)\n",
    "\n",
    "    @torch.no_grad()\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        output = self.dropout(o)\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSInputDropout(.1)(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSInputDropout(.1)(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSTranslateX(RandTransform):\n",
    "    \"Moves a selected sequence window a random number of steps\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        seq_len = o.shape[-1]\n",
    "        lambd = np.random.beta(self.magnitude, self.magnitude)\n",
    "        lambd = min(lambd, 1 - lambd)\n",
    "        shift = int(round(seq_len * lambd))\n",
    "        if shift == 0 or shift == seq_len: return o\n",
    "        if np.random.rand() < 0.5: shift = -shift\n",
    "        new_start = max(0, shift)\n",
    "        new_end = min(seq_len + shift, seq_len)\n",
    "        start = max(0, -shift)\n",
    "        end = min(seq_len - shift, seq_len)\n",
    "        output = torch.zeros_like(o, dtype=o.dtype, device=o.device)\n",
    "        output[..., new_start : new_end] = o[..., start : end]\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSTranslateX()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomShift(RandTransform):\n",
    "    \"Shifts and splits a sequence\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.02, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        pos = int(round(np.random.randint(0, o.shape[-1]) * self.magnitude)) * (random.randint(0, 1)*2-1)\n",
    "        output = torch.cat((o[..., pos:], o[..., :pos]), dim=-1)\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSRandomShift()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSHorizontalFlip(RandTransform):\n",
    "    \"Flips the sequence along the x-axis\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=1., ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        output = torch.flip(o, [-1])\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSHorizontalFlip()(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSHorizontalFlip()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomTrend(RandTransform):\n",
    "    \"Randomly rotates the sequence along the z-axis\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        flat_x = o.reshape(o.shape[0], -1)\n",
    "        ran = flat_x.max(dim=-1, keepdim=True)[0] - flat_x.min(dim=-1, keepdim=True)[0]\n",
    "        trend = torch.linspace(0, 1, o.shape[-1], device=o.device) * ran\n",
    "        t = (1 + self.magnitude * 2 * (np.random.rand() - 0.5) * trend)\n",
    "        t -= t.mean(-1, keepdim=True)\n",
    "        if o.ndim == 3: t = t.unsqueeze(1)\n",
    "        output = o + t\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output\n",
    "\n",
    "TSRandomRotate = TSRandomTrend"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSRandomTrend()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSVerticalFlip(RandTransform):\n",
    "    \"Applies a negative value to the time sequence\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=1., ex=None, **kwargs):\n",
    "        self.magnitude, self.ex = magnitude, ex\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        return - o"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(TSVerticalFlip()(xb, split_idx=0).shape, xb.shape)\n",
    "test_ne(TSVerticalFlip()(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSResize(RandTransform):\n",
    "    \"Resizes the sequence length of a time series\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=-0.5, size=None, ex=None, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.size, self.ex, self.mode = magnitude, size, ex, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if self.magnitude == 0: return o\n",
    "        size = ifnone(self.size, int(round((1 + self.magnitude) * o.shape[-1])))\n",
    "        output = F.interpolate(o, size=size, mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    for sz in np.linspace(.2, 2, 10): test_eq(TSResize(sz)(xb, split_idx=0).shape[-1], int(round(xb.shape[-1]*(1+sz))))\n",
    "    test_ne(TSResize(1)(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomSize(RandTransform):\n",
    "    \"Randomly resizes the sequence length of a time series\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, ex=None, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.ex, self.mode = magnitude, ex, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        size_perc = 1 + random_half_normal() * self.magnitude * (-1 if random.random() > .5 else 1)\n",
    "        return F.interpolate(o, size=int(size_perc * o.shape[-1]), mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    seq_len_ = []\n",
    "    for i in range(100):\n",
    "        o = TSRandomSize(.5)(xb, split_idx=0)\n",
    "        seq_len_.append(o.shape[-1])\n",
    "    test_lt(min(seq_len_), xb.shape[-1])\n",
    "    test_gt(max(seq_len_), xb.shape[-1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomLowRes(RandTransform):\n",
    "    \"Randomly resizes the sequence length of a time series to a lower resolution\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=.5, ex=None, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.ex, self.mode = magnitude, ex, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        size_perc = 1 - (np.random.rand() * (1 - self.magnitude))\n",
    "        return F.interpolate(o, size=int(size_perc * o.shape[-1]), mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSDownUpScale(RandTransform):\n",
    "    \"Downscales a time series and upscales it again to previous sequence length\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.5, ex=None, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.ex, self.mode = magnitude, ex, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0 or self.magnitude >= 1: return o\n",
    "        output = F.interpolate(o, size=int((1 - self.magnitude) * o.shape[-1]), mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)\n",
    "        output = F.interpolate(output, size=o.shape[-1], mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    test_eq(TSDownUpScale()(xb, split_idx=0).shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomDownUpScale(RandTransform):\n",
    "    \"Randomly downscales a time series and upscales it again to previous sequence length\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=.5, ex=None, mode='nearest', **kwargs):\n",
    "        \"mode:  'nearest' | 'linear' | 'area'\"\n",
    "\n",
    "        if not test_interpolate(mode):\n",
    "            print(f\"self.__name__ will not be applied because {mode} interpolation is not supported by {default_device()}. You can try a different mode\")\n",
    "            magnitude = 0\n",
    "\n",
    "        self.magnitude, self.ex, self.mode = magnitude, ex, mode\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0 or self.magnitude >= 1: return o\n",
    "        scale_factor = 0.5 + 0.5 * np.random.rand()\n",
    "        output = F.interpolate(o, size=int(scale_factor * o.shape[-1]), mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)\n",
    "        output = F.interpolate(output, size=o.shape[-1], mode=self.mode, align_corners=None if self.mode in ['nearest', 'area'] else False)\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if test_interpolate('nearest'):\n",
    "    test_eq(TSRandomDownUpScale()(xb, split_idx=0).shape, xb.shape)\n",
    "    test_ne(TSDownUpScale()(xb, split_idx=0), xb)\n",
    "    test_eq(TSDownUpScale()(xb, split_idx=1), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSRandomConv(RandTransform):\n",
    "    \"\"\"Applies a convolution with a random kernel and random weights with required_grad=False\"\"\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.05, ex=None, ks=[1, 3, 5, 7], **kwargs):\n",
    "        self.magnitude, self.ex, self.ks = magnitude, ex, ks\n",
    "        self.conv = nn.Conv1d(1, 1, 1, bias=False)\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0 or self.ks is None: return o\n",
    "        ks = random_choice(self.ks, 1)[0] if is_listy(self.ks) else self.ks\n",
    "        c_in = o.shape[1]\n",
    "        weight = nn.Parameter(torch.zeros(c_in, c_in, ks, device=o.device, requires_grad=False))\n",
    "        nn.init.kaiming_normal_(weight)\n",
    "        self.conv.weight = weight\n",
    "        self.conv.padding = ks // 2\n",
    "        output = (1 - self.magnitude) * o + self.magnitude * self.conv(o)\n",
    "        if self.ex is not None: output[...,self.ex,:] = o[...,self.ex,:]\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(5):\n",
    "    o = TSRandomConv(magnitude=0.05, ex=None, ks=[1, 3, 5, 7])(xb, split_idx=0)\n",
    "    test_eq(o.shape, xb.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "class TSRandom2Value(RandTransform):\n",
    "    \"Randomly sets selected variables of type `TSTensor` to predefined value (default: np.nan)\"\n",
    "    order = 90\n",
    "    def __init__(self, magnitude=0.1, sel_vars=None, sel_steps=None, static=False, value=np.nan, **kwargs):\n",
    "        assert not (sel_steps is not None and static), \"you must choose either static or sel_steps\"\n",
    "        if is_listy(sel_vars) and is_listy(sel_steps):\n",
    "            sel_vars = np.asarray(sel_vars)[:, None]\n",
    "        self.sel_vars, self.sel_steps = sel_vars, sel_steps\n",
    "        if sel_vars is None:\n",
    "            self._sel_vars = slice(None)\n",
    "        else:\n",
    "            self._sel_vars = sel_vars\n",
    "        if sel_steps is None or static:\n",
    "            self._sel_steps = slice(None)\n",
    "        else:\n",
    "            self._sel_steps = sel_steps\n",
    "        self.magnitude, self.static, self.value = magnitude , static, value\n",
    "        super().__init__(**kwargs)\n",
    "\n",
    "    def encodes(self, o:TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0 or self.magnitude > 1: return o\n",
    "        if self.static:\n",
    "            if self.sel_vars is not None:\n",
    "                if self.magnitude == 1:\n",
    "                    o[:, self._sel_vars] = o[:, self._sel_vars].fill_(self.value)\n",
    "                    return o\n",
    "                else:\n",
    "                    vals = torch.zeros_like(o)\n",
    "                    vals[:, self._sel_vars] = torch.rand(*vals[:, self._sel_vars, 0].shape, device=o.device).unsqueeze(-1)\n",
    "            else:\n",
    "                if self.magnitude == 1:\n",
    "                    return o.fill_(self.value)\n",
    "                else:\n",
    "                    vals = torch.rand(*o.shape[:-1], device=o.device).unsqueeze(-1)\n",
    "        elif self.sel_vars is not None or self.sel_steps is not None:\n",
    "            if self.magnitude == 1:\n",
    "                o[:, self._sel_vars, self._sel_steps] = o[:, self._sel_vars, self._sel_steps].fill_(self.value)\n",
    "                return o\n",
    "            else:\n",
    "                vals = torch.zeros_like(o)\n",
    "                vals[:, self._sel_vars, self._sel_steps] = torch.rand(*vals[:, self._sel_vars, self._sel_steps].shape, device=o.device)\n",
    "        else:\n",
    "            if self.magnitude == 1:\n",
    "                return o.fill_(self.value)\n",
    "            else:\n",
    "                vals = torch.rand_like(o)\n",
    "        mask = vals > (1 - self.magnitude)\n",
    "        return o.masked_fill(mask, self.value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[0., 1., 0., 1., 1., 1., 0., 1., 1., 1.],\n",
       "         [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],\n",
       "         [0., 1., 1., 0., 0., 0., 1., 0., 0., 1.]],\n",
       "\n",
       "        [[1., 1., 1., 0., 0., 1., 1., 1., 0., 1.],\n",
       "         [0., 1., 1., 0., 1., 0., 0., 0., 0., 0.],\n",
       "         [1., 1., 0., 1., 0., 0., 1., 0., 0., 1.]]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2, 3, 10))\n",
    "TSRandom2Value(magnitude=0.5, sel_vars=None, sel_steps=None, static=False, value=0)(t, split_idx=0).data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1., 1., 1., 0., 0., 0., 1.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]],\n",
       "\n",
       "        [[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1., 1., 0., 0., 0., 0., 1.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2, 3, 10))\n",
    "TSRandom2Value(magnitude=0.5, sel_vars=[1], sel_steps=slice(-5, None), static=False, value=0)(t, split_idx=0).data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]],\n",
       "\n",
       "        [[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2, 3, 10))\n",
    "TSRandom2Value(magnitude=0.5, sel_vars=[1], sel_steps=None, static=True, value=0)(t, split_idx=0).data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]],\n",
       "\n",
       "        [[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2, 3, 10))\n",
    "TSRandom2Value(magnitude=1, sel_vars=1, sel_steps=None, static=False, value=0)(t, split_idx=0).data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "         [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]],\n",
       "\n",
       "        [[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "         [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2, 3, 10))\n",
    "TSRandom2Value(magnitude=1, sel_vars=[1,2], sel_steps=None, static=False, value=0)(t, split_idx=0).data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 0., 1., 0., 1., 0., 1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]],\n",
       "\n",
       "        [[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 0., 1., 0., 1., 0., 1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2, 3, 10))\n",
    "TSRandom2Value(magnitude=1, sel_vars=1, sel_steps=[1,3,5], static=False, value=0)(t, split_idx=0).data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 0., 1., 0., 1., 0., 1., 1., 1., 1.],\n",
       "         [1., 0., 1., 0., 1., 0., 1., 1., 1., 1.]],\n",
       "\n",
       "        [[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
       "         [1., 0., 1., 0., 1., 0., 1., 1., 1., 1.],\n",
       "         [1., 0., 1., 0., 1., 0., 1., 1., 1., 1.]]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2, 3, 10))\n",
    "TSRandom2Value(magnitude=1, sel_vars=[1,2], sel_steps=[1,3,5], static=False, value=0)(t, split_idx=0).data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[1., 1., 1., 1.],\n",
       "         [1., 1., 1., 1.],\n",
       "         [1., 1., 1., nan]],\n",
       "\n",
       "        [[nan, nan, 1., 1.],\n",
       "         [1., 1., 1., 1.],\n",
       "         [1., nan, nan, 1.]]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2,3,4))\n",
    "TSRandom2Value(magnitude=.5, sel_vars=[0,2])(t, split_idx=0).data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[1., 1., nan, 1.],\n",
       "         [1., 1., nan, nan],\n",
       "         [1., 1., 1., nan]],\n",
       "\n",
       "        [[1., 1., nan, 1.],\n",
       "         [1., 1., 1., 1.],\n",
       "         [1., 1., 1., nan]]])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2,3,4))\n",
    "TSRandom2Value(magnitude=.5, sel_steps=slice(2, None))(t, split_idx=0).data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/yw/1vck7tm93_z1z0bftrw65hbw0000gn/T/ipykernel_22669/3870966226.py:2: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)\n",
      "  test_gt(np.isnan(TSRandom2Value(magnitude=.5)(t, split_idx=0)).sum().item(), 0)\n",
      "/var/folders/yw/1vck7tm93_z1z0bftrw65hbw0000gn/T/ipykernel_22669/3870966226.py:4: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)\n",
      "  test_gt(np.isnan(TSRandom2Value(magnitude=.5, sel_vars=[0,2])(t, split_idx=0)[:, [0,2]]).sum().item(), 0)\n",
      "/var/folders/yw/1vck7tm93_z1z0bftrw65hbw0000gn/T/ipykernel_22669/3870966226.py:6: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)\n",
      "  test_eq(np.isnan(TSRandom2Value(magnitude=.5, sel_vars=[0,2])(t, split_idx=0)[:, 1]).sum().item(), 0)\n"
     ]
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2,3,100))\n",
    "test_gt(np.isnan(TSRandom2Value(magnitude=.5)(t, split_idx=0)).sum().item(), 0)\n",
    "t = TSTensor(torch.ones(2,3,100))\n",
    "test_gt(np.isnan(TSRandom2Value(magnitude=.5, sel_vars=[0,2])(t, split_idx=0)[:, [0,2]]).sum().item(), 0)\n",
    "t = TSTensor(torch.ones(2,3,100))\n",
    "test_eq(np.isnan(TSRandom2Value(magnitude=.5, sel_vars=[0,2])(t, split_idx=0)[:, 1]).sum().item(), 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TSMask2Value(RandTransform):\n",
    "    \"Randomly sets selected variables of type `TSTensor` to predefined value (default: np.nan)\"\n",
    "    order = 90\n",
    "    def __init__(self, mask_fn, value=np.nan, sel_vars=None, **kwargs):\n",
    "        self.sel_vars = sel_vars\n",
    "        self.mask_fn = mask_fn\n",
    "        self.value = value\n",
    "        super().__init__(**kwargs)\n",
    "\n",
    "    def encodes(self, o:TSTensor):\n",
    "        mask = self.mask_fn(o)\n",
    "        if self.sel_vars is not None:\n",
    "            mask[:, self.sel_vars] = False\n",
    "        return o.masked_fill(mask, self.value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/yw/1vck7tm93_z1z0bftrw65hbw0000gn/T/ipykernel_22669/2303918966.py:4: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)\n",
      "  test_gt(np.isnan(TSMask2Value(_mask_fn)(t, split_idx=0)).sum().item(), 0)\n"
     ]
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2,3,100))\n",
    "def _mask_fn(o, r=.15, value=np.nan):\n",
    "    return torch.rand_like(o) > (1-r)\n",
    "test_gt(np.isnan(TSMask2Value(_mask_fn)(t, split_idx=0)).sum().item(), 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def self_mask(o):\n",
    "    mask1 = torch.isnan(o)\n",
    "    mask2 = rotate_axis0(mask1)\n",
    "    return torch.logical_and(mask2, ~mask1)\n",
    "\n",
    "\n",
    "class TSSelfDropout(RandTransform):\n",
    "    \"\"\"Applies dropout to a tensor with nan values by rotating axis=0 inplace\"\"\"\n",
    "    order = 90\n",
    "    def encodes(self, o: TSTensor):\n",
    "        mask = self_mask(o)\n",
    "        o[mask] = np.nan\n",
    "        return o"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/yw/1vck7tm93_z1z0bftrw65hbw0000gn/T/ipykernel_22669/2944010346.py:4: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)\n",
      "  nan_perc = np.isnan(t).float().mean().item()\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(0.3100000023841858, 0.5166666507720947)"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = TSTensor(torch.ones(2,3,100))\n",
    "mask = torch.rand_like(t) > .7\n",
    "t[mask] = np.nan\n",
    "nan_perc = np.isnan(t).float().mean().item()\n",
    "t2 = TSSelfDropout()(t, split_idx=0)\n",
    "test_gt(torch.isnan(t2).float().mean().item(), nan_perc)\n",
    "nan_perc, torch.isnan(t2).float().mean().item()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "all_TS_randaugs = [\n",
    "\n",
    "    TSIdentity,\n",
    "\n",
    "    # Noise\n",
    "    (TSMagAddNoise, 0.1, 1.),\n",
    "    (TSGaussianNoise, .01, 1.),\n",
    "    (partial(TSMagMulNoise, ex=0), 0.1, 1),\n",
    "    (partial(TSTimeNoise, ex=0), 0.1, 1.),\n",
    "    (partial(TSRandomFreqNoise, ex=0), 0.1, 1.),\n",
    "    partial(TSShuffleSteps, ex=0),\n",
    "    (TSRandomTimeScale, 0.05, 0.5),\n",
    "    (TSRandomTimeStep, 0.05, 0.5),\n",
    "    (partial(TSFreqDenoise, ex=0), 0.1, 1.),\n",
    "    (TSRandomLowRes, 0.05, 0.5),\n",
    "    (TSInputDropout, 0.05, .5),\n",
    "\n",
    "    # Magnitude\n",
    "    (partial(TSMagWarp, ex=0), 0.02, 0.2),\n",
    "    (TSMagScale, 0.2, 1.),\n",
    "    (partial(TSMagScalePerVar, ex=0), 0.2, 1.),\n",
    "    (partial(TSRandomConv, ex=0), .05, .2),\n",
    "    partial(TSBlur, ex=0),\n",
    "    partial(TSSmooth, ex=0),\n",
    "    partial(TSDownUpScale, ex=0),\n",
    "    partial(TSRandomDownUpScale, ex=0),\n",
    "    (TSRandomTrend, 0.1, 0.5),\n",
    "    TSVerticalFlip,\n",
    "    (TSVarOut, 0.05, 0.5),\n",
    "    (TSCutOut, 0.05, 0.5),\n",
    "\n",
    "    # Time\n",
    "    (partial(TSTimeWarp, ex=0), 0.02, 0.2),\n",
    "    (TSWindowWarp, 0.05, 0.5),\n",
    "    (TSRandomSize, 0.05, 1.),\n",
    "    TSHorizontalFlip,\n",
    "    (TSTranslateX, 0.1, 0.5),\n",
    "    (TSRandomShift, 0.02, 0.2),\n",
    "    (TSRandomZoomIn, 0.05, 0.5),\n",
    "    (TSWindowSlicing, 0.05, 0.2),\n",
    "    (TSRandomZoomOut, 0.05, 0.5),\n",
    "    (TSRandomLookBackOut, 0.1, 1.),\n",
    "    (TSRandomResizedLookBack, 0.1, 1.),\n",
    "    (TSTimeStepOut, 0.01, 0.2),\n",
    "    (TSRandomCropPad, 0.05, 0.5),\n",
    "    (TSRandomResizedCrop, 0.05, 0.5),\n",
    "    (TSMaskOut, 0.01, 0.2),\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class RandAugment(RandTransform):\n",
    "    order = 90\n",
    "    def __init__(self, tfms:list, N:int=1, M:int=3, **kwargs):\n",
    "        '''\n",
    "        tfms   : list of tfm functions (not called)\n",
    "        N      : number of tfms applied to each batch (usual values 1-3)\n",
    "        M      : tfm magnitude multiplier (1-10, usually 3-5). Only works if tfms are tuples (tfm, min, max)\n",
    "        kwargs : RandTransform kwargs\n",
    "        '''\n",
    "        super().__init__(**kwargs)\n",
    "        if not isinstance(tfms, list): tfms = [tfms]\n",
    "        self.tfms, self.N, self.magnitude = tfms, min(len(tfms), N), M / 10\n",
    "        self.n_tfms, self.tfms_idxs = len(tfms), np.arange(len(tfms))\n",
    "\n",
    "    def encodes(self, o:(NumpyTensor, TSTensor)):\n",
    "        if not self.N or not self.magnitude: return o\n",
    "        tfms = self.tfms if self.n_tfms==1 else L(self.tfms)[random_choice(np.arange(self.n_tfms), self.N, replace=False)]\n",
    "        tfms_ = []\n",
    "        for tfm in tfms:\n",
    "            if isinstance(tfm, tuple):\n",
    "                t, min_val, max_val = tfm\n",
    "                tfms_ += [t(magnitude=self.magnitude * float(max_val - min_val) + min_val)]\n",
    "            else:  tfms_ += [tfm()]\n",
    "        output = compose_tfms(o, tfms_, split_idx=self.split_idx)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_ne(RandAugment(TSMagAddNoise, N=5, M=10)(xb, split_idx=0), xb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class TestTfm(RandTransform):\n",
    "    \"Utility class to test the output of selected tfms during training\"\n",
    "    def __init__(self, tfm, magnitude=1., ex=None, **kwargs):\n",
    "        self.tfm, self.magnitude, self.ex = tfm, magnitude, ex\n",
    "        self.tfmd, self.shape = [], []\n",
    "        super().__init__(**kwargs)\n",
    "    def encodes(self, o: TSTensor):\n",
    "        if not self.magnitude or self.magnitude <= 0: return o\n",
    "        output = self.tfm(o, split_idx=self.split_idx)\n",
    "        self.tfmd.append(torch.equal(o, output))\n",
    "        self.shape.append(o.shape)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "def get_tfm_name(tfm):\n",
    "    if isinstance(tfm, tuple): tfm = tfm[0]\n",
    "    if hasattr(tfm, \"func\"): tfm = tfm.func\n",
    "    if hasattr(tfm, \"__name__\"): return tfm.__name__\n",
    "    elif hasattr(tfm, \"__class__\") and hasattr(tfm.__class__, \"__name__\"): return tfm.__class__.__name__\n",
    "    else: return tfm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_eq(get_tfm_name(partial(TSMagScale()))==get_tfm_name((partial(TSMagScale()), 0.1, .05))==get_tfm_name(TSMagScale())==get_tfm_name((TSMagScale(), 0.1, .05)), True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "all_TS_randaugs_names = [get_tfm_name(t) for t in all_TS_randaugs]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": "IPython.notebook.save_checkpoint();",
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/Users/nacho/notebooks/tsai/nbs/010_data.transforms.ipynb saved at 2025-07-29 10:41:33\n",
      "Correct notebook to script conversion! 😃\n",
      "Tuesday 29/07/25 10:41:36 CEST\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "                <audio  controls=\"controls\" autoplay=\"autoplay\">\n",
       "                    <source src=\"data:audio/wav;base64,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\" type=\"audio/wav\" />\n",
       "                    Your browser does not support the audio element.\n",
       "                </audio>\n",
       "              "
      ],
      "text/plain": [
       "<IPython.lib.display.Audio object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#|eval: false\n",
    "#|hide\n",
    "from tsai.export import get_nb_name; nb_name = get_nb_name(locals())\n",
    "from tsai.imports import create_scripts; create_scripts(nb_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
